Skip to content

Management API: Add endpoints to sort the children of a document or media item by a system field#23077

Open
AndyButland wants to merge 5 commits into
v17/devfrom
v17/feature/sort-children-by-field
Open

Management API: Add endpoints to sort the children of a document or media item by a system field#23077
AndyButland wants to merge 5 commits into
v17/devfrom
v17/feature/sort-children-by-field

Conversation

@AndyButland
Copy link
Copy Markdown
Contributor

@AndyButland AndyButland commented Jun 5, 2026

Description

Adds Management API endpoints to sort the children of a node by a chosen system field (name, create date, update date) and direction — an "auto-arrange" that persists the result as the children's sort order. Scoped to Documents and Media.

New endpoints

All take a JSON body and return 200 OK on success, 400 Bad Request if the field is unrecognised, or 404 Not Found if the parent doesn't exist.

Method & route Sorts
PUT /umbraco/management/api/v1/document/{id}/sort-children children of a document
PUT /umbraco/management/api/v1/document/root/sort-children root-level documents
PUT /umbraco/management/api/v1/media/{id}/sort-children children of a media item
PUT /umbraco/management/api/v1/media/root/sort-children root-level media

Request body:

// Document
{ "field": "name" | "createDate" | "updateDate",
  "direction": "Ascending" | "Descending",   // required
  "culture": "en-US" }                       // optional

// Media (no culture)
{ "field": "createDate", "direction": "Descending" }

Handling variants

For variant handling I've looked to match the existing manual drag-drop "sort children" UX, which displays the variant name but the node-level create date.

As such the provided culture only affects sorting by name. When provided, variant children are ordered by their name in that culture; invariant children, or variant children with no name for that culture, fall back to the invariant name.

Create/update dates are node-level, not culture-specific.

Other notes

  • Ordering is performed in the database, the same way the collection/list view orders its items.
  • Limited to system fields that live in a database table (name / create date / update date). Property-data fields I've considered as out of scope, since children need not share a type, so may not have the same properties.
  • Sorting a node with no children is a successful no-op (200).
  • Unrecognised field values are rejected at model binding (the field is a typed enum), and defensively mapped to 400 at the service layer.
  • Culture is not validated, and falls back to invariant if not found.
  • Persistence is optimised for potentially large child sets and, by default, differs from the manual drag-drop sort — see Sorting large numbers of children below.

Sorting large numbers of children

The manual drag-and-drop sort is naturally self-limiting: an editor has to load and reorder every child in the client before submitting, so it's impractical to trigger on huge sets. This endpoint is different — it can reorder a node's entire set of children from a single button click, regardless of how many there are, and a field sort typically moves most of them. Reusing the standard per-item sort would mean loading every child into memory and issuing (in the worst case) one UPDATE and one save/sort notification — and therefore one webhook — per child.

To keep this cheap and safe by default, a field sort is persisted differently from the manual sort:

  • The new order is computed in the database (reusing the same ordering as the list view) and applied with a single set-based UPDATE in the repository (IContentRepository.UpdateSortOrder, batched only to stay within the SQL Server parameter limit). The editing service collects the ordered child ids by paging — it does not hold every child in memory.
  • A single branch cache refresh and a single audit entry are emitted instead of per-item ones. The branch refresh is sufficient because the published cache reads sort order live from umbracoNode.
  • Consequently, no per-item save/sort notifications (and therefore no webhooks) are fired for a field sort by default.

For the small number of cases that depend on those per-item notifications/webhooks, the previous behaviour can be restored with the Umbraco:CMS:Content:SortChildrenByFieldFiresNotifications setting (default false). When enabled, the field sort goes through the standard per-item sort path instead, accepting the additional performance cost on nodes with many children. The combined audit entry and single branch refresh are used in both cases — only the per-item notifications differ.

Testing

Automated

Integration tests have been added at the service level to verify the ordering functionality.

Controller authorization tests for all four endpoints are also added.

Manual

Prerequisites: run the site and sign in to the backoffice — this authenticates the Swagger UI via the auth cookie. (Any other client must send the backoffice credentials, i.e. credentials: include.)

  1. Open Swagger UI at https://localhost:44339/umbraco/swagger/index.html (adjust the port to your launch settings) and select the Management API definition. The new operations are under the Document and Media tags, ending in /sort-children.

  2. Set up content to sort. Create a parent document with a handful of children whose names are deliberately out of order (e.g. "Banana", "Apple", "Cherry") and, ideally, different create/update dates. For variant testing, use a culture-varying document type with two languages and give the children different names per culture.

  3. Sort the children of a nodePUT /umbraco/management/api/v1/document/{id}/sort-children, where {id} is the parent's key:

    { "field": "name", "direction": "Ascending" }

    Expect 200 OK, then refresh the tree / list view under the parent and confirm the new order.

  4. Sort root-level itemsPUT /umbraco/management/api/v1/document/root/sort-children (no id), same body.

  5. Variant check (documents only). Repeat step 3 with a culture:

    { "field": "name", "direction": "Ascending", "culture": "da-DK" }

    Confirm the order reflects the Danish names. Call again with "culture": "en-US" and confirm the order changes to the English names.

  6. Dates. Call with "field": "createDate" or "updateDate" and confirm ordering by the node-level dates (a culture has no effect on these).

  7. Media. Repeat against /media/{id}/sort-children and /media/root/sort-children. The media body has no culture.

  8. Error cases.

    • Unknown parent: PUT /document/{random-guid}/sort-children404 Not Found.
    • Unrecognised field: body { "field": "bogus", "direction": "Ascending" }400 Bad Request.

Documentation

Need to document the new SortChildrenByFieldFiresNotifications setting.

Related

References #19703.

Copilot AI review requested due to automatic review settings June 5, 2026 10:14
@claude
Copy link
Copy Markdown

claude Bot commented Jun 5, 2026

Claude finished @AndyButland's task in 7m 29s —— View job


PR Review

Target: origin/v17/dev · Based on commit: ee8545d2 · Skipped: 1 file (OpenApi.json — noise)

Adds four Management API endpoints (PUT /document/{id}/sort-children, PUT /document/root/sort-children, PUT /media/{id}/sort-children, PUT /media/root/sort-children) that persist sorted child order by a system field (name, create date, update date), delegating the actual ordering to the database via the existing GetPagedChildren ordering infrastructure.

  • Modified public API: IContentEditingService + IMediaEditingService — new SortByFieldAsync method added; ContentEditingServiceWithSortingBase.GetPagedChildren — signature extended with Ordering? ordering parameter (internal, no external impact)
  • Affected implementations (outside this PR): None — both concrete services are internal sealed; the interface default (throw new NotImplementedException()) protects any external implementations
  • Breaking changes: None — new interface methods have default implementations; the GetPagedChildren signature change is internal
  • Other changes: New ContentSortField enum (Name, CreateDate, UpdateDate) in Umbraco.Core.Models.ContentEditing; four new controller routes in the Management API

Important

  • src/Umbraco.Core/Services/IContentEditingService.cs:108: Missing // TODO (V19): Remove default implementation. — the codebase uses this comment to mark throw new NotImplementedException() defaults as trackable (see IDocumentUrlService.cs:78). The new method should follow the same pattern. → Add the comment. (Same issue on IMediaEditingService.cs:131.)

Suggestions

  • tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaEditingServiceTests.SortByField.cs:85: Missing Sort_Children_By_Field_With_No_Children_Returns_Success and Can_Sort_Root_Media_By_Field tests that exist for content. The no-children early-return guard and the parentKey = null code path are covered for content but not media. → Consider adding these two for symmetry and completeness.

Approved with Suggestions for improvement

This is solid, well-structured work. The design is consistent with existing patterns (controller-per-operation, authorization via IAuthorizationService, database-driven ordering matching the list view), the test strategy is thorough — particularly the deliberate three-different-orderings approach that prevents coincidental passes — and the PR description clearly explains the culture semantics. The two findings above are minor and don't block merging.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Management API “auto-arrange” capability for Documents and Media: new PUT .../sort-children endpoints sort a parent’s children (or root-level items) by a selected system field (name/create date/update date) and persist the resulting order as SortOrder, matching list-view/database ordering semantics.

Changes:

  • Introduces ContentSortField and SortByFieldAsync(...) on content/media editing services, implemented via shared sorting base logic that loads children ordered by a DB Ordering and then persists that order.
  • Adds four Management API controllers (document/media + root variants) and corresponding request models + OpenAPI spec entries.
  • Adds integration tests covering service-level sorting behavior (content + media) and controller authorization for the new endpoints.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj Adds new partial test file to project item metadata (DependentUpon grouping).
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.SortByField.cs Integration tests for content sorting-by-field incl. culture-aware name sorting and root-level case.
tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaEditingServiceTests.SortByField.cs Integration tests for media sorting-by-field scenarios.
tests/Umbraco.Tests.Integration/ManagementApi/Document/SortChildrenDocumentControllerTests.cs Authorization coverage for document {id}/sort-children endpoint.
tests/Umbraco.Tests.Integration/ManagementApi/Document/SortChildrenAtRootDocumentControllerTests.cs Authorization coverage for document root/sort-children endpoint.
tests/Umbraco.Tests.Integration/ManagementApi/Media/SortChildrenMediaControllerTests.cs Authorization coverage for media {id}/sort-children endpoint.
tests/Umbraco.Tests.Integration/ManagementApi/Media/SortChildrenAtRootMediaControllerTests.cs Authorization coverage for media root/sort-children endpoint.
src/Umbraco.Core/Models/ContentEditing/ContentSortField.cs Adds typed enum for supported system sort fields.
src/Umbraco.Core/Services/IContentEditingService.cs Adds SortByFieldAsync to content editing service contract (default interface impl).
src/Umbraco.Core/Services/IMediaEditingService.cs Adds SortByFieldAsync to media editing service contract (default interface impl).
src/Umbraco.Core/Services/ContentEditingServiceWithSortingBase.cs Implements shared HandleSortByFieldAsync and adds ordering-aware child paging.
src/Umbraco.Core/Services/ContentEditingService.cs Wires SortByFieldAsync and passes ordering through to GetPagedChildren.
src/Umbraco.Core/Services/MediaEditingService.cs Wires SortByFieldAsync and passes ordering through to GetPagedChildren.
src/Umbraco.Cms.Api.Management/ViewModels/Sorting/SortChildrenByFieldRequestModelBase.cs Adds base request model containing field and direction for the new endpoints.
src/Umbraco.Cms.Api.Management/ViewModels/Sorting/SortDocumentChildrenByFieldRequestModel.cs Document-specific request model adds optional culture.
src/Umbraco.Cms.Api.Management/ViewModels/Sorting/SortMediaChildrenByFieldRequestModel.cs Media-specific request model (no culture).
src/Umbraco.Cms.Api.Management/Controllers/Document/SortChildrenDocumentController.cs New document-by-id sort-children endpoint + authorization + status mapping.
src/Umbraco.Cms.Api.Management/Controllers/Document/SortChildrenAtRootDocumentController.cs New root document sort-children endpoint + authorization + status mapping.
src/Umbraco.Cms.Api.Management/Controllers/Media/SortChildrenMediaController.cs New media-by-id sort-children endpoint + authorization + status mapping.
src/Umbraco.Cms.Api.Management/Controllers/Media/SortChildrenAtRootMediaController.cs New root media sort-children endpoint + authorization + status mapping.
src/Umbraco.Cms.Api.Management/OpenApi.json Adds the four new paths and request schemas + ContentSortFieldModel.

Comment thread src/Umbraco.Core/Services/IContentEditingService.cs Outdated
Comment thread src/Umbraco.Core/Services/IMediaEditingService.cs Outdated
@AndyButland AndyButland marked this pull request as draft June 5, 2026 10:53
@AndyButland AndyButland marked this pull request as ready for review June 5, 2026 12:28
@AndyButland AndyButland requested a review from Copilot June 5, 2026 12:28
@claude
Copy link
Copy Markdown

claude Bot commented Jun 5, 2026

Claude finished @AndyButland's task in 12m 58s —— View job


PR Review

Target: origin/v17/dev · Based on commit: efddf6cb · Skipped: 1 file (OpenApi.json)

Adds 4 Management API endpoints (PUT {id}/sort-children, PUT root/sort-children for both documents and media) that sort children by a system field (Name, CreateDate, UpdateDate) using a single set-based SQL UPDATE … CASE WHEN …, batched at MaxParameterCount / 2 to stay within SQL Server's 2100-parameter ceiling.

  • Modified public API: IContentEditingService.SortByFieldAsync, IMediaEditingService.SortByFieldAsync, IContentService.SortChildren, IMediaService.SortChildren, IContentRepository.UpdateSortOrder (all new methods), ContentSortField enum (new), ContentSettings.SortChildrenByFieldFiresNotifications (new config property)
  • Affected implementations (outside this PR): Any external implementors of IContentEditingService, IMediaEditingService, IContentService, IMediaService, or IContentRepository — all five get a default throw new NotImplementedException() via Pattern 3, so existing binary consumers will not break, but will throw at runtime if the new methods are invoked against them.
  • Breaking changes: None. All five new interface methods use Pattern 3 (default interface implementation with throw new NotImplementedException()) with correct // TODO (V19) removal schedule. ✅
  • Other changes: New opt-in config flag ContentSettings.SortChildrenByFieldFiresNotifications (default false). When false (default), per-item ContentSortingNotification / ContentSortedNotification are suppressed in favour of a single branch cache refresh — existing notification handlers won't fire unless operators set this flag.

Suggestions

  • src/Umbraco.Core/Services/ContentEditingService.cs:410 (inline): OperationResultToOperationStatus in the base class has no NoOperation arm — currently harmless because HandleSortByFieldAsync guards against empty lists before reaching SortChildrenInBulk, but if that guard were bypassed ContentService.SortChildren would return NoOperationUnknown → HTTP 500. Adding OperationResultType.NoOperation => ContentEditingOperationStatus.Success to the switch in ContentEditingServiceBase (line 456) eliminates the silent gap.

Approved with Suggestions for improvement

Good to go, but please carefully consider the importance of the suggestions.

@AndyButland AndyButland added the status/needs-docs Requires new or updated documentation label Jun 5, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 3 comments.

Comment thread src/Umbraco.Core/Services/MediaEditingService.cs Outdated
Comment thread src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs
Comment thread src/Umbraco.Core/Services/ContentEditingService.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/backend category/api status/needs-docs Requires new or updated documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants